package broker

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/golang/protobuf/proto"
	"go-micro.dev/v4/broker"
	"go-micro.dev/v4/client"
	"manager/bll"
	"manager/client/grpc"
	log "manager/collector/logger"
	"manager/lib/infra"
	cache "manager/lib/infra"
	"manager/lib/trace_log"
	"manager/model"
	"manager/model/entity"
	"manager/model/po"
	"manager/store/postgres"
	"manager/store/redis"
	"manager/utils"
	"proto/dto"
	"time"
)

const (
	EnShock               = "shock"
	EnRotation            = "rotation"
	EnPressure            = "pressure"
	EnDump                = "dump"
	EnLowPower            = "low_power"
	EnBatteryDisconnected = "battery_disconnected"
	EnNoGps               = "no_gps"
)

var AlarmReport = &alarmReport{}

// alarmReport 告警上报
type alarmReport struct {
}

// HandlerAlarm 处理告警
func (a *alarmReport) HandlerAlarm(data *dto.Alarm) error {
	if data == nil {
		return fmt.Errorf("alarmReport HandlerAlarm data is nil")
	}

	// 警告类型
	alarmCode := ""
	score := 0
	// 震动
	if data.Shock != nil {
		alarmCode = EnShock
		score = int(*data.Shock)
	}

	// 轮动
	if data.Rotation != nil {
		alarmCode = EnRotation
		score = int(*data.Rotation)
	}

	// 压力
	if data.Pressure != nil {
		alarmCode = EnPressure
		score = int(*data.Pressure)
	}

	// 倾倒 （加速度计）
	if data.Dump != nil {
		alarmCode = EnDump
		score = int(*data.Dump)
	}

	// 低电量
	if data.LowPower != nil {
		alarmCode = EnLowPower
		score = int(*data.LowPower)
	}

	// 电池断开
	if data.BatteryDisconnected != nil {
		alarmCode = EnBatteryDisconnected
		score = int(*data.BatteryDisconnected)
	}

	// 长时间无GPS定位
	if data.NoGps != nil {
		alarmCode = EnNoGps
		score = int(*data.NoGps)
	}

	if alarmCode == "" {
		return fmt.Errorf("alarmReport HandlerAlarm alarmCode is empty")
	}

	// 获取车辆id
	bikeInfo, err := bll.CacheBike.GetBikeInfoBySn(data.Sn)
	if err != nil {
		return err
	}

	// 获取车辆类型
	bikeType, err := bll.CacheBikeType.GetBikeTypeId(bikeInfo.TypeID)
	if err != nil {
		return err
	}

	// 获取告警类型
	info, err := bll.CacheAlarmType.GetAlarmType(alarmCode)
	if err != nil {
		return err
	}

	if info == nil {
		return fmt.Errorf("alarmReport HandlerAlarm alarmType is nil")
	}

	// 获取车辆主账号
	masterId := bll.UserEbike.GetManagerByBikeId(bikeInfo.ID)
	if masterId == 0 {
		trace_log.CommonLogger.Warn("alarmReport HandlerAlarm masterId is empty，没有车主的车辆 sn:", data.Sn, " alarmContent:", a.getContentByCode(alarmCode))
		return nil
	}

	// 创建告警
	id, err := postgres.Alarm.Create(context.Background(), &entity.Alarm{
		UserId:        masterId,
		Type:          info.Id,
		EbikeId:       bikeInfo.ID,
		EbikeTypeId:   bikeInfo.TypeID,
		EbikeTypeName: bikeType.Name,
		FrameCode:     bikeInfo.SNCode,
		Level:         0,
		Score:         score,
	})

	// 创建告警通知
	if a.isToggle(masterId, bikeInfo.ID, alarmCode) {
		bll.MessageNotify.Create(context.Background(), &model.MessageNotifyCreateRequest{
			EbikeId:     bikeInfo.ID,
			EbikeName:   bikeInfo.Name,
			MessageType: 2,
			Title:       bikeInfo.Name,
			Content:     a.getContentByCode(alarmCode),
			Flag:        0,
			Extend:      "",
			FrameCode:   bikeInfo.FrameCode,
			RelationId:  id,
		})

	}

	// 消息通知里面，倾倒就告警
	if a.isStealAlarm(masterId, bikeInfo.ID, alarmCode) {
		bll.MessageNotify.Create(context.Background(), &model.MessageNotifyCreateRequest{
			EbikeId:     bikeInfo.ID,
			EbikeName:   bikeInfo.Name,
			MessageType: 2,
			Title:       bikeInfo.Name,
			Content:     "车辆发生异常情况，疑似被盗，请及时查看！",
			Flag:        0,
			Extend:      "",
			FrameCode:   bikeInfo.FrameCode,
			RelationId:  id,
		})
	}

	// 执行报警
	go func() {
		defer utils.ExceptionCatch()
		a.alarmTrigger(bikeInfo.ID, bikeInfo.SNCode, alarmCode)
	}()
	return err
}

// TriggerStealAlarm 触发被盗告警
func (a *alarmReport) TriggerStealAlarm(sn string, location *dto.Location) (err error) {
	defer utils.ExceptionCatch()
	defer func() {
		if err != nil {
			trace_log.DebugLogger(sn, "TriggerStealAlarm GetShadowAttributes error:", err)
		}
	}()

	bikeId, _ := bll.CacheBike.GetBikeIdBySn(sn)
	if bikeId <= 0 {
		return fmt.Errorf("TriggerStealAlarm bikeId is empty")
	}

	// 设备状态
	bikeStatus := bll.Ebike.DeviceStatus(context.Background(), bikeId)
	if bikeStatus.PowerStatus > 0 || bikeStatus.OnlineStatus == 0 {
		return nil
	}

	// 10分钟内有定位就不告警
	if redis.KV.ValueExist(context.Background(), "GuardFence", sn) {
		//trace_log.DebugLogger(sn, "TriggerStealAlarm GuardFence is true")
		return nil
	}

	var bikeSettings *entity.Setting
	if settings, ok := cache.GCache.Get(fmt.Sprintf("%v", bikeId)); !ok {
		// 查询车辆设置
		bikeSettings, err = postgres.Setting.Find(&model.SettingUpdateRequest{
			EbikeId: &bikeId,
		})

		cache.GCache.Set(fmt.Sprintf("%v", bikeId), bikeSettings, time.Second*30)
	} else {
		bikeSettings = settings.(*entity.Setting)
	}

	// 如果没有设置告警，就不告警
	if bikeSettings != nil && bikeSettings.EbikeSet != nil && bikeSettings.EbikeSet.Alarm == 0 {
		return nil
	}

	// 进行计算车辆当前位置和设防的位置，触发被盗告警，距离超过100米就告警且车辆在线
	shadowRsp, err := grpc.SCmd.GetShadowAttributes(context.Background(), &dto.GetShadowAttributesRequest{
		DeviceName: sn,
	})
	if err != nil {
		return
	}

	type shadowAttribute struct {
		StopLocation string `json:"stop_location"`
	}
	// 解析影子
	var attribute shadowAttribute
	if shadowRsp.Attribute == "" {
		return
	}

	// 解析
	err = json.Unmarshal([]byte(shadowRsp.Attribute), &attribute)
	if err != nil {
		return
	}

	lo := po.Point{}
	err = json.Unmarshal([]byte(attribute.StopLocation), &lo)
	if err != nil {
		return err
	}

	if lo.Y == 0 || lo.X == 0 {
		return fmt.Errorf("TriggerStealAlarm lo is empty")
	}

	// 计算距离
	distance := infra.Lbs.GeoDistance(lo.X, lo.Y, float64(location.Lng), float64(location.Lat), "K")
	if distance < 100 {
		return nil
	}

	// 查询车辆信息
	bikeInfo, err := bll.CacheBike.GetBikeInfoBySn(sn)
	if err != nil {
		return err
	}

	// 创建警告
	bll.MessageNotify.Create(context.Background(), &model.MessageNotifyCreateRequest{
		EbikeId:     bikeId,
		EbikeName:   bikeInfo.Name,
		MessageType: 2,
		Title:       bikeInfo.Name,
		Content:     "您的车辆出现疑似被盗风险，请留意！",
		Flag:        0,
		Extend:      "",
		FrameCode:   bikeInfo.FrameCode,
		RelationId:  0,
	})
	trace_log.DebugLogger(sn, "TriggerStealAlarm success")

	// 10分钟内不重复报警
	redis.KV.AddValueWithExpireTime(context.Background(), "GuardFence", sn, 1, time.Second*60*10)
	return nil
}

// alarmTrigger 告警触发
func (a *alarmReport) isStealAlarm(userId int64, bikeId int64, code string) bool {
	if userId <= 0 {
		bikeInfo, _ := bll.CacheBike.GetBikeInfoById(bikeId)
		trace_log.DebugLogger(bikeInfo.SNCode, "alarmReport isStealAlarm userId is empty")
		return false
	}

	info, err := postgres.UserSetting.Find(context.Background(), &model.UserSettingInfoRequest{
		UserId: &userId,
	})
	if err != nil || info == nil {
		return false
	}

	return (code == EnDump) && info.GuardAlarm > 0
}

// isToggle 是否是开关
func (a *alarmReport) isToggle(userId int64, bikeId int64, code string) bool {
	if userId <= 0 {
		bikeInfo, _ := bll.CacheBike.GetBikeInfoById(bikeId)
		trace_log.DebugLogger(bikeInfo.SNCode, "alarmReport isToggle userId is empty")
		return false
	}

	info, err := postgres.UserSetting.Find(context.Background(), &model.UserSettingInfoRequest{
		UserId: &userId,
	})
	if err != nil || info == nil {
		return false
	}

	switch code {
	case EnShock:
		return info.Vibration > 0
	case EnRotation:
		return true
	case EnPressure:
		return true
	case EnDump:
		return info.GuardAlarm > 0
	case EnLowPower:
		return info.CutPower > 0
	case EnBatteryDisconnected:
		return true
	case EnNoGps:
		return true
	}
	return true
}

// getContentByCode 根据code获取内容
func (a *alarmReport) getContentByCode(code string) string {
	switch code {
	case EnShock:
		return "车辆发生震动"
	case EnRotation:
		return "车辆发生轮动"
	case EnPressure:
		return "车辆发生压力"
	case EnDump:
		return "车辆发生倾倒"
	case EnLowPower:
		return "充电未充满断开"
	case EnBatteryDisconnected:
		return "车辆电池断开"
	case EnNoGps:
		return "车辆长时间无GPS定位"
	}
	return ""
}

// ElectronicFenceTrigger 电子围栏
func (a *alarmReport) ElectronicFenceTrigger(bikeId int64, sn string) {
	defer utils.ExceptionCatch()
	var (
		info *entity.Setting
		err  error
	)

	// 查询缓存
	if settings, ok := cache.GCache.Get(fmt.Sprintf("settings_%v", bikeId)); !ok {
		// 查询车辆设置
		info, err = postgres.Setting.Find(&model.SettingUpdateRequest{
			EbikeId: &bikeId,
		})

		if err != nil {
			log.Errorf("alarmReport alarmTrigger Find error: %v", err)
			return
		}

		cache.GCache.Set(fmt.Sprintf("settings_%v", bikeId), info, time.Second*30)
	} else {
		info = settings.(*entity.Setting)
	}

	// 电子围栏未开启
	if info == nil || info.EbikeSet == nil || info.EbikeSet.ElectronicFence == 0 {
		return
	}

	// 10分钟内不重复报警
	if redis.KV.ValueExist(context.Background(), "ElectronicFence", sn) {
		//trace_log.DebugLogger(sn, "ElectronicFenceTrigger 10分钟内不重复报警")
		return
	}

	// 获取车辆位置
	rsp, _ := grpc.SCmd.GetShadowAttributeKeys(context.Background(), &dto.GetShadowAttributeKeysRequest{
		DeviceName: sn,
		Key:        "location",
	})

	var point po.Point
	err = json.Unmarshal([]byte(rsp.Value), &point)
	if err != nil {
		log.Errorf("alarmReport alarmTrigger Unmarshal error: %v", err)
		return
	}

	// 计算距离
	distance := infra.Lbs.GeoDistance(info.EbikeSet.FenceCenter.X, info.EbikeSet.FenceCenter.Y, point.X, point.Y, "K")

	// 默认50米
	if info.EbikeSet.FenceRadius == 0 {
		info.EbikeSet.FenceRadius = 10000
	}

	//trace_log.DebugLogger(sn, " ElectronicFenceTrigger distance:", distance, " settings:", utils.ToJsonString(info.EbikeSet), " point:", utils.ToJsonString(point))
	// 超出半径执行报警
	if int(distance) > info.EbikeSet.FenceRadius {

		// 获取车辆id
		bikeInfo, _ := bll.CacheBike.GetBikeInfoBySn(sn)
		if bikeInfo == nil {
			log.Errorf("alarmReport alarmTrigger GetBikeInfoBySn bikeInfo is nil")
			return
		}

		// 获取车辆类型
		bikeType, _ := bll.CacheBikeType.GetBikeTypeId(bikeInfo.TypeID)
		if bikeType == nil {
			return
		}

		// 创建告警记录
		id, _ := postgres.Alarm.Create(context.Background(), &entity.Alarm{
			UserId:        bll.UserEbike.GetManagerByBikeId(bikeInfo.ID),
			Type:          info.Id,
			EbikeId:       bikeInfo.ID,
			EbikeTypeId:   bikeInfo.TypeID,
			EbikeTypeName: bikeType.Name,
			FrameCode:     bikeInfo.SNCode,
			Level:         0,
			Score:         0,
		})

		// todo: 执行报警
		bll.MessageNotify.Create(context.Background(), &model.MessageNotifyCreateRequest{
			EbikeId:     bikeInfo.ID,
			EbikeName:   bikeInfo.Name,
			MessageType: 2,
			Title:       bikeInfo.Name,
			Content:     "车辆超出电子围栏",
			Flag:        0,
			Extend:      "",
			FrameCode:   bikeInfo.FrameCode,
			RelationId:  id,
		})

		trace_log.DebugLogger(sn, "alarmTrigger 车辆超出电子围栏 settings:", utils.ToJsonString(info.EbikeSet), " point:", utils.ToJsonString(point))

		// 给车辆下发超出电子围栏通知
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()
		bytes, _ := hex.DecodeString("fe03aa6901")
		err = bll.RpcCall(sn, func(addr string, err error) error {
			if err != nil {
				return err
			}

			if _, err = grpc.Cmd.BinaryReq(ctx, &dto.BinaryRequest{
				Sn:  sn,
				Hex: bytes,
			}, func(options *client.CallOptions) {
				options.Address = []string{addr}
			}); err != nil {
				trace_log.DebugLogger(sn, "车辆超出围栏下发指令失败 err:", err)
			} else {
				trace_log.DebugLogger(sn, "车辆超出围栏下发指令成功")
				data, _err := proto.Marshal(&dto.DeviceStatus{
					Sn:    sn,
					Event: dto.EventType_Lock,
				})
				if _err == nil {
					Consumer.Handle(&publication{
						m: &broker.Message{
							Body: data,
						},
						t: "signal.event.status",
					})
				}
			}

			// 10分钟内不重复报警
			redis.KV.AddValueWithExpireTime(context.Background(), "ElectronicFence", sn, 1, time.Second*60*5)
			return err
		})
	}
	return
}

// alarmTrigger 告警触发
func (a *alarmReport) alarmTrigger(bikeId int64, sn, alarmCode string) {
	if alarmCode != EnShock && alarmCode != EnDump && alarmCode != EnRotation {
		return
	}

	//a.ElectronicFenceTrigger(bikeId, sn)
	return
}
